﻿Namespace FileSystemScan
    Friend Class RescanWorker
        Private Shared mWatchThread As Threading.Thread

        Public Shared Sub Start()
            If mWatchThread Is Nothing Then
                'register filesystem watchers

                SetupEvents(True)

                For Each clsShare As Entities.Share In EntityHandlers.Shares.GetAll
                    Try
                        For Each strPath As String In clsShare.Paths
                            If IO.Directory.Exists(strPath) Then
                                Dim clsFSW As New System.IO.FileSystemWatcher(strPath)
                                clsFSW.IncludeSubdirectories = clsShare.Recursive
                                clsFSW.NotifyFilter = IO.NotifyFilters.LastWrite _
                                                   Or IO.NotifyFilters.FileName _
                                                   Or IO.NotifyFilters.DirectoryName
                                SetupWatcherEvents(clsFSW, True)
                                clsFSW.EnableRaisingEvents = True
                                mWatchers.Add(clsFSW)
                            End If
                        Next
                    Catch ex As Exception
                        FileShare.Engine.Manager.OnLogEvent(String.Format("Failed setting up file system watchers for ""{0}"": {1}", clsShare.Name, ex.Message))
                    End Try
                Next

                mWatchThread = New Threading.Thread(AddressOf WatchWorker)
                mWatchThread.Name = "File Watch Thread"
                mWatchThread.Priority = Threading.ThreadPriority.Lowest
                mWatchThread.IsBackground = True
                mWatchThread.Start()
            End If
        End Sub

        Public Shared Sub [Stop](ByVal message As Framework.Message)
            If mWatchThread IsNot Nothing Then
                message.SetMessage("Shutting down file system watchers...")
                For Each clsWatcher As IO.FileSystemWatcher In mWatchers
                    clsWatcher.EnableRaisingEvents = False
                    SetupWatcherEvents(clsWatcher, False)
                Next
                SetupEvents(False)
                mWatchers.Clear()
                mWatchThread.Abort()
                mWatchThread.Join(10000)
                mWatchThread = Nothing
            End If
        End Sub

        Private Shared Sub SetupWatcherEvents(ByVal watcher As IO.FileSystemWatcher, ByVal start As Boolean)
            If start Then
                AddHandler watcher.Renamed, AddressOf EventHandler_Renamed
                AddHandler watcher.Changed, AddressOf EventHandler_Changed
                AddHandler watcher.Created, AddressOf EventHandler_Created
                AddHandler watcher.Deleted, AddressOf EventHandler_Deleted
            Else
                RemoveHandler watcher.Renamed, AddressOf EventHandler_Renamed
                RemoveHandler watcher.Changed, AddressOf EventHandler_Changed
                RemoveHandler watcher.Created, AddressOf EventHandler_Created
                RemoveHandler watcher.Deleted, AddressOf EventHandler_Deleted
            End If
        End Sub

        Private Shared Sub SetupEvents(ByVal start As Boolean)
            If start Then
                AddHandler EntityHandlers.Shares.DeletedGUI, AddressOf EventHandler_ShareDeleted
                AddHandler EntityHandlers.Shares.UpdatedGUI, AddressOf EventHandler_ShareUpdated
            Else
                RemoveHandler EntityHandlers.Shares.DeletedGUI, AddressOf EventHandler_ShareDeleted
                RemoveHandler EntityHandlers.Shares.UpdatedGUI, AddressOf EventHandler_ShareUpdated
            End If
        End Sub

        Private Shared mWatchers As New Generic.List(Of IO.FileSystemWatcher)
        Private Shared mCurrentShare As Entities.Share
        Private Shared Sub WatchWorker()
            Try
                'start by queuing a scan of all watched folders for changes that might have occured
                'while application wasn't running
                Try
                    Dim aWatches As Generic.List(Of Entities.Share)
                    aWatches = EntityHandlers.Shares.GetAll

                    For Each clsShare As Entities.Share In aWatches
                        FileShare.Engine.Manager.RescanShare(clsShare, TimeSpan.FromSeconds(5))
                    Next
                Catch ex As Exception
                    FileShare.Engine.Manager.OnLogEvent(String.Format("Error scanning folders: {0}", ex.Message), ex)
                End Try

                Dim dtNextBlockPurge As Date = Now.AddSeconds(60)
                Do
                    Try
                        Dim clsItem As RescanQueue.QueueItem = RescanQueue.GetNextItemToScan
                        If clsItem IsNot Nothing Then
                            Try
                                mCurrentShare = clsItem.Share
                                ScanManager.ScanShareForChanges(clsItem.Share, clsItem.PathToScan)
                            Catch ex As System.IO.IOException
                                FileShare.Engine.Manager.OnLogEvent(String.Format("Error scanning path ""{0}"": {1}", clsItem.PathToScan, ex.Message), ex)
                                If clsItem.RetryCount < 30 Then
                                    'requeue the item for later
                                    RescanQueue.ReQueue(clsItem)
                                Else
                                    FileShare.Engine.Manager.OnLogEvent(String.Format("Gave up scanning path ""{0}"" after {1} failed attempts!", clsItem.PathToScan, clsItem.RetryCount), ex)
                                End If
                            Finally
                                mCurrentShare = Nothing
                            End Try
                        Else
                            Threading.Thread.Sleep(1000)
                        End If
                        If Now > dtNextBlockPurge Then
                            RescanBlock.PurgeOldBlocks()
                            dtNextBlockPurge = Now.AddSeconds(60)
                        End If

                    Catch ex As Threading.ThreadAbortException When TypeOf ex.ExceptionState Is Entities.Share
                        Dim clsShare As Entities.Share = CType(ex.ExceptionState, Entities.Share)
                        'we were just asked to cancel the scan of the current share
                        FileShare.Engine.Manager.OnLogEvent(String.Format("Scan of share {0} aborted since share was modified", clsShare.Name))
                        Threading.Thread.ResetAbort()
                    Catch ex As Threading.ThreadAbortException
                        Exit Do
                    Catch ex As Exception
                        FileShare.Engine.Manager.OnLogEvent(String.Format("Error occured while scanning folder! {0}", ex.Message), ex)
                    End Try
                Loop
            Catch ex As Threading.ThreadAbortException
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent(ex.Message, ex)
            End Try
        End Sub

        Private Shared Sub EventHandler_ShareDeleted(ByVal share As Entities.Share)
            AbortIfShareIsBeingScanned(share)
        End Sub
        Private Shared Sub EventHandler_ShareUpdated(ByVal share As Entities.Share)
            AbortIfShareIsBeingScanned(share)
        End Sub
        Private Shared Sub AbortIfShareIsBeingScanned(ByVal share As Entities.Share)
            Try
                Try
                    Threading.Thread.BeginCriticalRegion()

                    Dim clsCurrentShare As Entities.Share = mCurrentShare
                    If clsCurrentShare IsNot Nothing AndAlso clsCurrentShare.Id = share.Id Then
                        mWatchThread.Abort(clsCurrentShare)
                    End If
                Finally
                    Threading.Thread.EndCriticalRegion()
                End Try

            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error: " & ex.Message, ex)
            End Try
        End Sub


        Private Shared Function FindShareByPath(ByVal path As String) As Entities.Share
            For Each clsShare As Entities.Share In EntityHandlers.Shares.GetAll
                For Each strPath As String In clsShare.Paths
                    If Framework.IO.Utils.IsSubDir(path, strPath) Then
                        Return clsShare
                    End If
                Next
            Next
            Return Nothing
        End Function

        Private Shared Sub EventHandler_Renamed(ByVal sender As Object, ByVal e As IO.RenamedEventArgs)
            Try
                Dim blnIgnored As Boolean = True
                If Not (RescanBlock.IsBlocked(e.OldFullPath) Or RescanBlock.IsBlocked(e.FullPath)) Then
                    If ScanFolder_Common.IncludeFile(e.OldFullPath) Then
                        Dim clsShare As Entities.Share = FindShareByPath(e.OldFullPath)
                        RescanQueue.Queue(clsShare, IO.Path.GetDirectoryName(e.OldFullPath), TimeSpan.FromSeconds(10))
                        blnIgnored = False
                    End If
                    If ScanFolder_Common.IncludeFile(e.FullPath) Then
                        Dim clsShare As Entities.Share = FindShareByPath(e.FullPath)
                        RescanQueue.Queue(clsShare, IO.Path.GetDirectoryName(e.FullPath), TimeSpan.FromSeconds(10))
                        blnIgnored = False
                    End If
                End If
                Manager.OnFileSysWatcherTrace("Rescan", "Renamed", e.OldFullPath & " to " & e.FullPath, blnIgnored)
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error: " & ex.Message, ex)
            End Try
        End Sub
        Private Shared Sub EventHandler_Changed(ByVal sender As Object, ByVal e As IO.FileSystemEventArgs)
            Try
                Dim blnIgnored As Boolean = True
                If IO.File.Exists(e.FullPath) Then
                    If Not RescanBlock.IsBlocked(e.FullPath) Then
                        If ScanFolder_Common.IncludeFile(e.FullPath) Then
                            Dim clsShare As Entities.Share = FindShareByPath(e.FullPath)
                            RescanQueue.Queue(clsShare, IO.Path.GetDirectoryName(e.FullPath), TimeSpan.FromSeconds(10))
                            blnIgnored = False
                        End If
                    End If
                End If
                Manager.OnFileSysWatcherTrace("Rescan", "Changed", e.FullPath, blnIgnored)
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error: " & ex.Message, ex)
            End Try
        End Sub
        Private Shared Sub EventHandler_Created(ByVal sender As Object, ByVal e As IO.FileSystemEventArgs)
            Try
                Dim blnIgnored As Boolean = True
                If Not RescanBlock.IsBlocked(e.FullPath) Then
                    If ScanFolder_Common.IncludeFile(e.FullPath) Then
                        Dim clsShare As Entities.Share = FindShareByPath(e.FullPath)
                        RescanQueue.Queue(clsShare, IO.Path.GetDirectoryName(e.FullPath), TimeSpan.FromSeconds(10))
                        blnIgnored = False
                    End If
                End If
                Manager.OnFileSysWatcherTrace("Rescan", "Created", e.FullPath, blnIgnored)
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error: " & ex.Message, ex)
            End Try
        End Sub
        Private Shared Sub EventHandler_Deleted(ByVal sender As Object, ByVal e As IO.FileSystemEventArgs)
            Try
                Dim blnIgnored As Boolean = True
                If Not RescanBlock.IsBlocked(e.FullPath) Then
                    If ScanFolder_Common.IncludeFile(e.FullPath) Then

                        Dim clsShare As Entities.Share = FindShareByPath(e.FullPath)
                        RescanQueue.Queue(clsShare, IO.Path.GetDirectoryName(e.FullPath), TimeSpan.FromSeconds(10))
                        blnIgnored = False
                    End If
                End If
                Manager.OnFileSysWatcherTrace("Rescan", "Deleted", e.FullPath, blnIgnored)
            Catch ex As Exception
                FileShare.Engine.Manager.OnLogEvent("Error: " & ex.Message, ex)
            End Try
        End Sub

        Private Shared Function SkipWatcherEvent(ByVal filePath As String) As Boolean
            Return False
        End Function

        
    End Class
End Namespace